<?php
namespace backend\components;

use teach\util\helper\ResponseHelper;
use Yii;
use yii\base\Component;
use yii\db\Exception;

/**
 * 小微商户function
 * Class WeChatSmall
 * @package backend\components
 */
class WeChatSmall extends Component
{
    //use \backend\components\Certificate;

    const WXAPIHOST = 'https://api.mch.weixin.qq.com/';
    // 服务商商户号
    protected $mch_id;
    // 商户证书序列号
    protected $serial_no;
    // 加密秘钥
    protected $aes_key;
    // 商户自定义key
    protected $diy_key;
    protected $uid;

    public function __construct()
    {
        parent::__construct();
        $this->mch_id = Yii::$app->params[''];
        $this->serial_no = Yii::$app->params[''];
        $this->aes_key = Yii::$app->params[''];
        $this->diy_key = Yii::$app->params[''];
        $this->uid = '';
    }

    public function applyEnter(array $params)
    {
        //校验参数
        if (!$this->checkParams($params)) {
            return ResponseHelper::buildJsonData(400,'参数不齐');
        }

        //校验银行卡号前缀是否支持
        if ($this->accountNumberIsSupport($params['account_number'] ?? '')) {
            return ResponseHelper::buildJsonData(400,'银行卡不支持');
        }

        if ($this->applyIsExist($params['id_card_number'], $params['id_card_name']) ) {
            return ResponseHelper::buildJsonData(400, '请勿重复提交信息,已在审核中,请直接查询状态');
        }

        $data = [
            'version' => '3.0',
            'cert_sn' => $this->newResponseData()['serial_no'],
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->getRandChar(),
            'sign_type' => 'HMAC-SHA256',
            'sign' => '',
            'business_code' => $this->getBusinessCode(), // 业务申请编号
            'id_card_copy' => $params['id_card_copy'], // 身份证人像面照片  media_id
            'id_card_national' => $params['id_card_national'], // 身份证国徽面照片
            'id_card_name' => $this->publicKeyEncrypt($params['id_card_name']),
            'id_card_number' => $this->publicKeyEncrypt($params['id_card_number']),
            'id_card_valid_time' => $params['id_card_valid_time'], // '["1970-01-01","长期"]' string(50)
            'account_name' => $this->publicKeyEncrypt($params['account_name']),
            'account_bank' => $params['account_bank'],
            'bank_address_code' => $params['bank_address_code'],
            'bank_name' => $params['bank_name'] ?? '',
            'account_number' => $this->publicKeyEncrypt($params['account_number']),
            'store_name' => $params['store_name'],
            'store_address_code' => $params['store_address_code'],
            'store_street' => $params['store_street'],
            'store_longitude' => $params['store_longitude'] ?? '',
            'store_latitude' => $params['store_latitude'] ?? '',
            'store_entrance_pic' => $params['store_entrance_pic'],
            'indoor_pic' => $params['indoor_pic'],
            'address_certification' => $params['address_certification'] ?? '',
            'merchant_shortname' => $params['merchant_shortname'],
            'service_phone' => $params['service_phone'],
            'business' => $params['business'],
            'product_desc' => $params['product_desc'] ?? '',
            'qualifications' => $params['qualifications'] ?? '',
            'rate' => $params['rate'] ?? '0.6%',
            'business_addition_desc' => $params['business_addition_desc'] ?? '',
            'business_addition_pics' => $params['business_addition_pics'] ?? '', // ["123","456"] 最多可上传5张照片，请填写已预先上传图片生成好的MediaID
            'contact' => $this->publicKeyEncrypt($params['contact']),
            'contact_phone' => $this->publicKeyEncrypt($params['contact_phone']),
            'contact_email' => isset($params['contact_email']) && !empty($params['contact_email']) ? $this->publicKeyEncrypt($params['contact_email']) : '',
        ];

    }

    /**
     * applyIsExist 查询申请是否存在且申请状态是成功
     * @param $id_card_number
     * @param $id_card_name
     * @return bool
     */
    public function applyIsExist($id_card_number, $id_card_name) {
        //TODO 查询此人是否存在
        $res = XwApplyEnter::where(['id_card_number' => $id_card_number, 'id_card_name' => $id_card_name, 'uid' => $this->uid])->orderBy('created_at', 'desc')->first();
        if (empty($res)) {
            return false;
        }
        $applyment_state = '';
        // 已经提交成功后判断状态，状态不是驳回则返回true,否则返回false
        if (empty($res->sub_mch_id)) {
            // 商户号不存在代表没有查询过或者查询过状态不为完成或待签约，重新请求查询状态接口
            $rt = $this->enquiryOfApplyStatus(['applyment_id' => $res->applyment_id]);
            $applyment_state = $rt['applyment_state'];
        } else {
            $applyment_state = $res->applyment_state;
        }
        // 状态如果是被驳回则可插入新数据
        if ($applyment_state != 'REJECTED') {
            return true;
        }
        return false;
    }

    /**
     * enquiryOfApplyStatus 入驻申请状态查询
     * @param array $params
     * @return array
     * @throws \Exception
     */
    public function enquiryOfApplyStatus(array $params) {
        if (!isset($params['applyment_id']) && !isset($params['business_code'])) {
            throw new \Exception('applyment_id或者business_code缺少参数');
        }
        $data = [
            'version' => '1.0',
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->getRandChar(),
            'sign_type' => 'HMAC-SHA256',
            'sign' => '',
            'applyment_id' => $params['applyment_id'] ?? '',
            'business_code' => $params['business_code'] ?? '',
        ];
        $data['sign'] = $this->makeSign($data, $data['sign_type']);
        $url = self::WXAPIHOST . 'applyment/micro/getstate';
        $xml = $this->toXml($data);
        // 发起入驻申请请求
        $res = $this->httpsRequest($url, $xml, [], true);
        // 处理返回值
        $rt = $this->disposeReturn($res, [
            'applyment_id',
            'applyment_state',
            'applyment_state_desc',
            'sub_mch_id',
            'sign_url',
            'audit_detail',
        ], ['business_code' => $data['business_code']]);
        $audit_detail = $rt['audit_detail'];
        $rt['audit_detail'] = json_decode($audit_detail, true);

        //TODO
        XwApplyEnter::where(['applyment_id' => $rt['applyment_id'], 'uid' => $this->uid])->update([
            'applyment_state' => $rt['applyment_state'],
            'applyment_state_desc' => $rt['applyment_state_desc'],
            'sub_mch_id' => $rt['sub_mch_id'],
            'audit_detail' => $audit_detail,
        ]);
        return $rt;
    }

    /**
     * 校验入驻字段
     * @param array $params
     * @return bool
     */
    private function checkParams(array $params)
    {
        $data = ['id_card_copy', 'id_card_national', 'id_card_name', 'id_card_number', 'id_card_valid_time', 'account_name', 'account_bank', 'bank_address_code', 'account_number', 'store_name', 'store_address_code', 'store_street', 'store_entrance_pic', 'indoor_pic', 'merchant_shortname', 'service_phone', 'business', 'contact', 'contact_phone'];
        $result = true;
        foreach ($data as $key => $value) {
            if (!isset($params[$value]) || empty($params[$value])) {
                $result = false;
                break;
            }
        }
        return $result;
    }

    /**
     * 判断银行卡号是否支持
     * @param string $account_number
     * @return bool
     */
    private function accountNumberIsSupport(string $account_number)
    {
        $account_prefix_6 = substr($account_number, 0, 6);
        $account_prefix_8 = substr($account_number, 0, 8);

        $not_support = ['623501', '621468', '620522', '625191', '622384', '623078', '940034', '622150', '622151', '622181', '622188', '955100', '621095', '620062', '621285', '621798', '621799', '621797', '622199', '621096', '62215049', '62215050', '62215051', '62218849', '62218850', '62218851', '621622', '623219', '621674', '623218', '621599', '623698', '623699', '623686', '621098', '620529', '622180', '622182', '622187', '622189', '621582', '623676', '623677', '622812', '622810', '622811', '628310', '625919', '625368', '625367', '518905', '622835', '625603', '625605', '518905'];
        if (array_search($account_prefix_6, $not_support)) {
            return true;
        }
        if (array_search($account_prefix_8, $not_support)) {
            return true;
        }
        return false;
    }



    /**
     * 内置封装的方法
     */

    /**
     * @param $url
     * @param string $data
     * @param array $headers
     * @param bool $userCert
     * @param int $timeout
     * @return array
     */
    protected function httpsRequest($url, $data = '', array $headers = [], $userCert = false, $timeout = 30)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        if (!empty($data)) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        //设置超时
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
        if ($userCert) {
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);//严格校验
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            list($sslCertPath, $sslKeyPath) = $this->getSSLCertPath();
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($curl, CURLOPT_SSLKEY, $sslKeyPath);
        } else {
            if (substr($url, 0, 5) == 'https') {
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 信任任何证书
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 检查证书中是否设置域名
            }
        }
        if (!empty($headers)) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
            // curl_setopt($curl, CURLINFO_HEADER_OUT, true); //TRUE 时追踪句柄的请求字符串，从 PHP 5.1.3 开始可用。这个很关键，就是允许你查看请求header
            // $headers = curl_getinfo($curl, CURLINFO_HEADER_OUT); //官方文档描述是“发送请求的字符串”，其实就是请求的header。这个就是直接查看请求header，因为上面允许查看
        }
        curl_setopt($curl, CURLOPT_HEADER, true);    // 是否需要响应 header
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output          = curl_exec($curl);
        $header_size     = curl_getinfo($curl, CURLINFO_HEADER_SIZE);    // 获得响应结果里的：头大小
        $response_header = substr($output, 0, $header_size);    // 根据头大小去获取头信息内容
        $http_code       = curl_getinfo($curl, CURLINFO_HTTP_CODE);    // 获取响应状态码
        $response_body   = substr($output, $header_size);
        $error           = curl_error($curl);
        curl_close($curl);

        return [$response_body, $http_code, $response_header, $error];
    }

    /**
     * parseHeaders    处理curl响应头
     * @param $header
     * @return array
     */
    protected function parseHeaders($header)
    {
        $headers = explode("\r\n", $header);
        $head    = array();
        array_map(function($v) use (&$head) {
            $t = explode(':', $v, 2);
            if (isset($t[1])) {
                $head[trim($t[0])] = trim($t[1]);
            } else {
                if (!empty($v)) {
                    $head[] = $v;
                    if (preg_match("#HTTP/[0-9\.]+\s+([0-9]+)#", $v, $out)) {
                        $head['reponse_code'] = intval($out[1]);
                    }
                }
            }
        }, $headers);
        return $head;
    }

    /**
     * getRandChar 获取随机字符串
     * @param int $length
     * @return null|string
     */
    protected function getRandChar($length = 32)
    {
        $str    = NULL;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $newStr = str_shuffle($strPol);
        $max    = strlen($strPol) - 1;
        for ($i = 0; $i < $length; $i++) {
            $str .= $newStr[mt_rand(0, $max)];    // rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }
        return $str;
    }

    /**
     * MakeSign 生成签名
     * @param $data
     * @param string $signType
     * @return string
     */
    protected function makeSign(array $data, $signType = 'HMAC-SHA256')
    {
        //签名步骤一：按字典序排序参数
        ksort($data);
        $string = $this->toUrlParams($data);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $this->diy_key;
        //签名步骤三：MD5加密或者HMAC-SHA256
        if ($signType == 'md5') {
            //如果签名小于等于32个,则使用md5验证
            $string = md5($string);
        } else {
            //是用sha256校验
            $string = hash_hmac("sha256", $string, $this->diy_key);
        }
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * ToUrlParams     格式化参数格式化成url参数
     * @param $data
     * @return string
     */
    protected function toUrlParams(array $data)
    {
        $buff = "";
        foreach ($data as $k => $v) {
            if ($k != "sign" && $v !== "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 检测签名
     * @param $data
     * @return bool
     * @throws \Exception
     */
    protected function checkSign($data)
    {
        strlen($data['sign']) <= 32 && $sign_type = 'md5';
        if ($this->makeSign($data, $sign_type ?? '') == $data['sign']) {
            return true;
        }
        throw new \Exception('检测签名错误');
    }

    protected function getSSLCertPath()
    {
        return [
            Yii::$app->params[''],//sslCertAddr
            Yii::$app->params[''], //privateKeyAddr
        ];
    }

    /**
     * 输出xml字符
     * @param $data
     * @return string
     * @throws \Exception
     */
    protected function toXml($data)
    {
        if (!is_array($data) || count($data) <= 0) {
            throw new \Exception('xml解析错误');
        }
        $xml = "<xml>";
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }


    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     * @throws \Exception
     */
    protected function fromXml($xml)
    {
        if (!$xml) {
            throw new \Exception('xml数据不能为空');
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xml_parser = xml_parser_create();
        if (!xml_parse($xml_parser, $xml, true)) {
            xml_parser_free($xml_parser);
            throw new \Exception('解析xml错误');
        } else {
            $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        }
        return $arr;
    }

    /**
     * getMillisecond 获取毫秒级别的时间戳
     * @return float
     */
    protected function getMillisecond()
    {
        list($msec, $sec) = explode(' ', microtime());
        $msectime = (float) sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
        return $msectime;
    }

    /**
     * disposeReturn 处理微信小微商户接口返回值
     * @param $res httpsRequest 方法调用接口返回的数组
     * @param array $need_fields $need_fields 需要接口返回的字段（当return_code 、result_code都为SUCCESS时返回的字段的key）
     * @param array $arr 自定义的参数返回出去，例如入驻接口生成的业务编号
     * @return array 微信返回系统级错误不暴露出去，直接返回操作失败，业务级别错误返回具体错误消息
     * @throws \Exception
     */
    protected function disposeReturn($res, array $need_fields = [], array $arr = [])
    {
        if ($res[1] == 200) {
            $rt = $this->fromXml($res[0]);
            if ($rt['return_code'] != 'SUCCESS') {
//                \Log::error($rt['return_msg']);
//                throw new WxException(0, $rt['return_msg']);
                throw new \Exception('微信返回信息错误');
            }
            if ($rt['result_code'] != 'SUCCESS') {
//                throw new WxException(0, $rt['err_code_des'] ?? $rt['err_code_msg']);
                throw new \Exception('微信返回信息错误');
            }
            if ($this->checkSign($rt)) {
                if (!empty($need_fields)) {
                    $need = [];
                    array_map(function($v) use ($rt, &$need) {
                        $need[$v] = $rt[$v] ?? '';
                    }, $need_fields);
                    return array_merge($need, $arr);
                }
                return $arr;
            }
        }
//        throw new WxException(30002);
        throw new \Exception('微信返回信息错误');
    }


}